#include "p8.h"
#include <cassert>
#include <cstddef>
#include <iostream>


void testHomeWork8_08() {
    int outlierIterCnt { 5 };

    while (outlierIterCnt > 0) {
        int innlierIterCnt = 0;
        while (innlierIterCnt < outlierIterCnt) {
            std::cout << outlierIterCnt - innlierIterCnt << ' ';
            ++innlierIterCnt;
        }
        std::cout << '\n';

        --outlierIterCnt;
    }

    int iterRowCnt { 5 };
    int iterColCnt { 5 };

    while (iterRowCnt > 0) {
        int startSpaceCnt { iterRowCnt - 1 };
        int spaceCnt { startSpaceCnt };
        int digitalCnt {iterColCnt - spaceCnt};

        while (spaceCnt > 0) {
            std::cout << ' ' << ' ';
            --spaceCnt;
        }

        while (digitalCnt > 0) {
            std::cout << digitalCnt << ' ';
            --digitalCnt;
        }
        std::cout << '\n';

        --iterRowCnt;
    }
}

void testRandom() {
    std::cout << Random::get(1, 6) << '\n';
    std::cout << Random::get(1u, 6u) << '\n';

    std::cout << Random::get<std::size_t>(1, 6u) << '\n';

    std::uniform_int_distribution die6{ 1, 6 }; // for C++14, use std::uniform_int_distribution<> die6{ 1, 6 };
    for (int count{ 1 }; count <= 10; ++count) {
        std::cout << die6(Random::mt) << '\t'; // generate a roll of the die here
    }

    std::cout << '\n';
}

namespace BallHeight {
    constexpr double gravity {9.8};

    // Gets tower height from user and returns it
    double getTowerHeight() {
        std::cout << "Enter the height of the tower in meters: ";
        double towerHeight{};
        std::cin >> towerHeight;
        return towerHeight;
    }

    // Returns the current ball height after "seconds" seconds
    constexpr double calculateBallHeight(double towerHeight, int seconds) {
        const double fallDistance { BallHeight::gravity * (seconds * seconds) / 2.0 };
        const double ballHeight { towerHeight - fallDistance };

        // If the ball would be under the ground, place it on the ground
        if (ballHeight < 0.0)
            return 0.0;

        return ballHeight;
    }

    // Prints ball height above ground
    void printBallHeight(double ballHeight, int seconds) {
        if (ballHeight > 0.0)
            std::cout << "At " << seconds << " seconds, the ball is at height: " << ballHeight << " meters\n";
        else
            std::cout << "At " << seconds << " seconds, the ball is on the ground.\n";
    }

    // Calculates the current ball height and then prints it
    // This is a helper function to make it easier to do this
    double calculateAndPrintBallHeight(double towerHeight, int seconds) {
        double ballHeight{ calculateBallHeight(towerHeight, seconds) };
        printBallHeight(ballHeight, seconds);

        return ballHeight;
    }

    void testBallHeight() {
        double towerHeight{ getTowerHeight() };

        int seconds { 0 };
        while (calculateAndPrintBallHeight(towerHeight, seconds) > 0.0)
        {
            ++seconds;
        }
    }
}


namespace IsPrime {
    bool isPrime(int n) {
        if (n < 2) {
            return false;
        }

        if (n == 2 || n == 3) {
            return true;
        }

        if (n % 2 == 0 || n % 3 == 0) {
            return false;
        }

        int sqrt_n = static_cast<int>(std::sqrt(n));
        for (int i = 5; i <= sqrt_n; i += 6) {
            if (n % i == 0 || n % (i + 2) == 0) {
                return false;
            }
        }

        return true;
    }


    // Modular exponentiation
    int modPow(int base, int exp, int mod) {
        int result = 1;
        base = base % mod; // Update base if it is more than mod
        while (exp > 0) {
            if (exp % 2 == 1)  // If exp is odd, multiply base with the result
                result = (result * base) % mod;
            exp = exp >> 1;  // exp = exp / 2
            base = (base * base) % mod;  // Change base to base^2 % mod
        }
        return result;
    }


    bool millerTest(int d, int n) {
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_real_distribution<> dis(2, n - 2);
        int a = dis(gen);

        int x = IsPrime::modPow(a, d, n);;
        if (x == 1 || x == n - 1) {
            return true;
        }

        while (d != n - 1) {
            x = (x * x) % n;
            d *= 2;

            if (x == 1) {
                return false;
            }

            if (x == n - 1) {
                return true;
            }
        }

        return false;
    }


    bool isPrimeByMillerRabinAlg(int n, int k) {
        if (n <= 1 || n == 4) {
            return false;
        }

        if (n <= 3) {
            return true;
        }

        int d = n - 1;
        while (d % 2 == 0) {
            d /= 2;
        }

        for (int i = 0; i < k; i++) {
            if (!IsPrime::millerTest(d, n)) {
                return false;
            }
        }

        return true;
    }

    void testPrimeList() {
        std::vector<int> testList = {1, 4, 6, 8, 10, 12, 14, 15, 16, 18, 20, 21};

        for (size_t idx = 0; idx < testList.size(); ++idx) {
            assert(!IsPrime::isPrime(testList[idx]));
            assert(!IsPrime::isPrimeByMillerRabinAlg(testList[idx], 5));
        }

        std::cout << "Success Done!\n";
    }

}


namespace GuessNumber {
    bool guessNumber(int guessCnt, int number) {
        for (int idx = 1; idx <= guessCnt; ++idx) {
            std::cout << "Guess #" << idx << ": ";
            int guessRes {};
            std::cin >> guessRes;

            if (guessRes > number) {
                std::cout << "Your guess is too high.\n";
            } else if (guessRes < number) {
                std::cout << "Your guess is too low.\n";
            } else {
                return true;
            }
        }
        return false;
    }

    bool guessAgain() {
        while (true) {
            char ch{};
            std::cout << "Would you like to play again (y/n)? ";
            std::cin >> ch;

            switch (ch) {
            case 'y':
                return true;
            case 'n':
                return false;
            }
        }
    }

    void playGame() {
        constexpr int playCnt { 7 };

        do {
            int number { Random::get(1, 100) };

            std::cout << "Let's play a game. I'm thinking of a number between 1 and 100. You have " << playCnt << " tries to guess what it is.\n";
            bool won { guessNumber(playCnt, number) };
            if (won)
                std::cout << "Correct! You win!\n";
            else
                std::cout << "Sorry, you lose. The correct number was " << number << '\n';
        } while (guessAgain());
    }
}
